Character Strings
The previous instalments of this column have described how to manipulate integer variables
with the SPEED/ASM package.

Now it’s time to explore some additional data types supported by SPEED/ASM.

For the next few months I'll discuss how you manipulate character strings,
the most important non-numeric type of data.

String Variable Allocation
In part one of this series I described how. to allocate storage for a string variable.
For the benefit of those who don’t have the April edition of inCider, I'll first review this procedure.

Unlike other data types in SPEED/ASM. (integer, real and character), string variables
require a variable amount of storage space.

The amount of space required depends entirely on how big you wish to allow the string to grow.
Strings in SPEED/ASM need n+ 2 bytes, where n represents the maximum number of characters,
up to 255, you will allow the string to contain.

The first byte of a SPEED/ASM variable must contain this value.
The next m bytes after that (where m <= n) must contain the characters that make up the string.
Finally, the first byte following the characters making up the string must contain 0,
which SPEED/ASM uses to mark the end of the character string.

Figure 1 shows the SPEED/ASM format for a string variable.

Before defining a string variable you must determine its maximum possible size.
If you try to store more characters into a string than you allow for, SPEED/ASM will
truncate the string and return an error code.

On the other hand, if you define a string using the maximum length (255 characters) when
at most only 20 characters are ever required, you're wasting a lot of memory space.

Obviously, some careful thought must go into deciding how much space to reserve.

The easiest way to do the reserving is to use LISA’s ADR and DFS pseudo opcodes.
The format for string declaration should be:

      <label> ADR <maxlength>
              DFS <maxlength>

where <label> is the name of the string variable you are declaring and <maxlength> is
the maximum string length. <maxlength> must be in the range 1-255.

This form of string declaration fulfills three functions:
it stores the maximum string length into the first byte (as required),
it stores 0 into the second byte (assuming <maxlength> is a value less than 255),
and it reserves <max-length> + 2 bytes as required for a SPEED/ASM string.

(Two bytes are reserved by the ADR pseudo opcode, <maxlength> bytes are reserved by the DFS statement.)

By storing 0 into the second byte of the string variable, this form of the string declaration
initializes the variable to the empty string.

The operation of this form of the declaration is depicted in Figure 2.

      ---------------------------------/-------/--------------------------------------
      |...|   |   |   |   |   |   |   /       /   |   |   |   |   |   |  |...|   |   |
      |...|   |   |   |   |   |   |   |      |    |   |   |   |   |   |  |...| Unused|
      |...| Characters within the |   |      |  string are stored here.  |.0.|Portion|
      |...|   |   |   |   |   |   |  /       /|   |   |   |   |   |   |  |...|   of  |
      |...|   |   |   |   |   |   | /       / |   |   |   |   |   |   |  |...| String|
      -----------------------------/-------/------------------------------------------
        ^                                                                  ^
        |                                                                  |
        |                                                                  |
   Maximum Length of String                                     Zero Terminating Byte
 is Stored in the First Byte
                              Figure 1. SPEED/ASM string format

                  -----------------      --------------------------
           STRNAME | ADR  <strlen> | ---> |...| 0 |                |
                   -----------------      --------------------------
                                                         ^
                   -----------------                     |
                   | DFS  <strlen> | ---------------------
                   -----------------
             The ADR pseudo-opcode stores <strlen> in the first byte (this is the maximum
             length value required by SPEED/ASM strings) and then stores a zero into the
             second byte. This zero initializes the string to the empty string.
             The DFS pseudo opcode reserves enough storage for the actual character
             string. SPEED/ASM strings require exactly <strlen> +2 bytes. By using both
             the ADR and DFS pseudo-ops in the fashion the required number of bytes
             are automatically allocated.

                   Figure 2..Operation of ADR/DFS pseudo opcodes for string declarations.

                 ----------------      ---------------------------------
         STRNAME | STR "STRING" | ---> | 6 | S | T | R | I | N | G | 0 |
                 ----------------      ---------------------------------
                                                                     ^
                 ----------------                                    |
                 | BYT 0        | ------------------------------------
                 ----------------
      The STR pseudo-opcode stores a length byte followed by the characters in the string.
      In this case the STR pseudo-op emits a six followed by the six characters "STRING".
      The BYT pseudo-opcode emits the zero required at the end of all SPEED/ASM strings.

           Figure 3. Using the STR and BYT pseudo opcodes to initialize a string variable.

                  JSR   LDSTR                      <strvar>
                      ------------         ------------------------------------/---/
                  ADR | <strvar> | ------> | 6 | s | t | r | i | n | g | 0 |  /   /|
                      ------------         ----------------------------------/---/--
                                                 ^   ^   ^   ^   ^   ^
                      ------------               |   |   |   |   |   |
                  BYT |"string",0| --------------|---|---|---|---|---|
                      ------------
              Figure 4. Operation of the LDSTR routine.

The LDSTR routine copies the zero terminated string into the string variable whose address
immediately follows the call to a length byte (which LDSTR computes), the six characters
“string” and the zero terminating byte.

Note that in this example eight characters are copied:
     a length byte (which LDSTR computes),
       the six characters "string" and the zero terminating byte.

Occasionally you may want to reserve space for a SPEED/ASM string variable and initialize
the string to some fixed value.

There are a couple of ways to accomplish this, depending on your requirements.
If the string value will never change (i.e., you will never store another string value into the variable)
then the following definition can be used:

      <label> STR “<character string>”
              BYT 0

where <label> is the name of the string variable and <character string> is the data you
wish to initialize the string to.

The STR pseudo opcode emits the length of the specified string followed by the string itself.
Since, in this case, the maximum possible length of the string is the length of the string,
the STR pseudo opcode automatically emits the proper data for the maximum value.

The BYT directive emits the required terminating 0 byte, since STR doesn’t do so.

This approach is pictured in Figure 3.

Using the STR method to declare an initial value for a string is great if the string value
doesn’t change during execution of the program,
(or if you can guarantee that the initial string value is the largest string value).

It is perfect for setting up such fixed strings as error messages and menus.
If you need to initialize a string whose initial length is not the maximum length the
string will grow to, you have to declare the string using the statements:

      <label> BYT  <maxlength>,“<string>”
              DFS  <maxlength> +1 —<length of string>,0

Rather than explaining how this complicated affair works, space is better spent describing
how to use SPEED/ASM routines to avoid such statements,

Initializing and Assigning Strings
The most basic string operation is the string assignment. SPEED/ASM supports two routines,
LDSTR and MOVS, for this purpose.

LDSTR copies a string constant. into a string variable,
  and MOVS copies one string variable into another.

The LDSTR routine (load a string) replaces Basic string assignments of the form:

      55 A$ = “STRING”

The syntax for the LDSTR is:

      JSR LDSTR
      ADR <deststr>
      BYT “<string constant>”,0

where <deststr> is the name of the string variable into which you want to store
the string <string constant>.

LDSTR should be used to initialize a string variable with a string that is shorter than
the maximum length. (See the problem in the last section.)

The operation of LDSTR is shown in Figure 4.

The MOVS routine copies the contents of one string variable into another.

This enables you to translate statements of the form:

      100 A$ = B$
into SPEED/ASM. The calling sequence for the MOVS routine is:

     JSR MOVS
     ADR <source string>,<destination string>

where <source string> is the name of the source string and <destination string> is the
name of the string you want the source string copied into.

The operation of MOVS is shown in Figure 5.
                                    ------------------------------------/---/
                         <strvar>:  | 6 | s | t | r | i | n | g | 0 |  /   /|
                                    ----------------------------------/---/--
                                      |   |   |   |   |   |   |   |
        JSR  MOVS                     |   |   |   |   |   |   |   |
        ADR <strvar>,<destvar>        |   |   |   |   |   |   |   |
                                      |   |   |   |   |   |   |   |
                                      \/  \/  \/  \/  \/  \/  \/  \/
                                    ------------------------------------/---/
                        <destvar>:  | 6 | s | t | r | i | n | g | 0 |  /   /|
                                    ----------------------------------/---/--
               Figure 5. Operation of the MOVS routine.

The only problem with using LDSTR and MOVS occurs when you try to store a source string
whose length is greater than the maximum length of the destination string.

In this case LDSTR and MOVS truncate the string and store the largest string that will fit.

Then LDSTR and MOVS return the 6502 overflow flag (V) set if such an error occurs.
Likewise, if the error does not occur, the V flag is returned clear.

So you can see if a string overflow occurred by checking the v flag
(using the BVS and BVC instructions) after a JSR to LDSTR and MOVS.

String I/O
SPEED/ASM provides three routines for performing string input/output.
These routines enable you to read a string from the keyboard and store it into a
string variable, print the contents of a string variable, and print a string constant.

To print a string constant use the PRINT routine, which should be quite familiar to you.
We've been employing it all along to print prompts onto the Apple’s video display.
PRINT needs the calling sequence:

      JSR PRINT
      BYT “<string>",0

Note that the string constant must be terminated with a 0 byte.

To print the contents of a string variable you can use the PRTSTR routine.

Type the statement:

      JSR PRTSTR
      ADR <string variable>

where <string variable> is the name of the string variable you wish to print.

Reading a string from the keyboard and storing it into a string variable is done by
the RDSTR routine.

RDSTR reads whatever data is present in the line input buffer up until a carriage return
is detected.

If the line buffer pointer is already pointing at a carriage return, a new line is read
from the keyboard.

The syntax for RDSTR is:

      JSR RDSTR
      ADR <string variable>

where <string variable> is the name of the string variable.

If the string entered by the user is too large for the specified string the V flag is
returned set;

      otherwise it is returned clear.

Additional Notes on Keyboard Input
The RDINT, RDSTR and-READLN routines all work together in the SPEED/ASM environment.

Some information on how they read data from the keyboard may be of help.

RDINT reads an integer from the current position in the line input buffer and leaves the
line buffer pointer pointing at the first character beyond the integer read in.

If the first character RDINT attempts to read is a carriage return, then RDINT first
calls READLN to read a line of text from the keyboard.

RDSTR reads a string from the input buffer starting at the current index into the line buffer.
The remainder of the line (up to the carriage return) is read in and stored into the specified string.

If the line buffer pointer was pointing at a carriage return when RDSTR was called,
then a line of text is first read from the keyboard.

If you want to ensure that a fresh line of text is read from the keyboard before reading
an integer or a string, you should call READLN immediately before calling RDSTR or RDINT.

For example:

      JSR READLN
      JSR RDINT
      ADR INTGR
      JSR READLN
      JSR RDSTR
      ADR STRVAR

If it is inconvenient to call READLN immediately before calling RDINT or RDSTR
(an example appears in the sample program, Listing 2), then store the value 0 into the
SPEED/ASM GOTIN variable. (This variable is predefined in the SPEED/ASM equates.)

By storing 0 (false) into GOTLN you can force the RDSTR and RDINT routines to read a new
line of text the next time they are executed,

such as:

      LDA #FALSE
      STA GOTLN
       .   .
       .   .
       .   .
      JSR RDSTR  ; A new line of text will automatically
      ADR STRING ; be read from the keyboard.

String Comparisons Using IFS0 and IFS
The IFS and IFS0 routines compare two strings in SPEED/ASM.

These routines are very similar to the IFI and IFI0 integer routines discussed earlier in
this series.

IFS compares two string variables, and IFS0 compares a string variable to a string constant.

Since IFS0 is used most often, I'll describe it first. The calling sequence is:

      JSR IFS0
      ADR <string variable>,<op>
      BYT “string constant”,0

where <string variable> is the name of a properly declared string variable, “string constant”
is the string you wish to compare the string variable to, and <op> is any of the following
SPEED/ASM comparisons:


      EQ      ; Equal
      NE      ; Not equal
      LT      ; Less than
      GT      ; Greater than
      LE      ; Less than or equal
      GE      ; Greater than or equal

These symbolic values are provided in the SPEED/ASM equates, Listing 1.

Upon return from the IFS0 routine the 6502 zero flag is set and the accumulator contains 0
if the comparison was not true.
If the comparison was true, the zero flag is returned clear and the accumulator contains 1.

This feature allows you to use LISA’s BTR and BFL branches to test the comparison.

The IFS routine is used to compare two SPEED/ASM string variables.

The calling sequence is:

      JSR IFS
      ADR <strl>,<op>,<str2>

where <str1> and <str2> are two SPEED/ASM variables that are to be compared to one another
and <op> is any of the operators listed in the previous paragraph.

Examples of IFS and IFS0 appear in Listing 2.

SPEED/ASM supports four string functions: SUBSTR, INDEX, LENGTH and CONCAT.
These four functions provide the basic string manipulations required by most programs.
LENGTH determines the current dynamic length of a string—that is, the number of characters
currently stored in the string variable.

To call LENGTH use:

      JSR LENGTH
      ADR <string>

where <string> is the name of the string variable whose length you wish to find.
Since the maximum length of a string is 255 characters (which fits into 1 byte) the length
is returned in the 6502 accumulator.

This enables you to easily compare the length to an immediate value like:

      JSR LENGTH
      ADR STRING
      CMP #55
      BGE STRGE55

      “The SUBSTR routine extracts a portion of a string and stores this substring into
       another string variable.”

If you want to store this value into a SPEED/ASM variable, you should store the accumulator
into the low-order byte of the SPEED/ASM variable and store 0 into the high-order byte of
the SPEED/ASM variable,
   
   as in this sequence:

      JSR LENGTH
      ADR STRING
      STA STRLEN
      LDA #0
      STA STRLEN+1

Note that the LENGTH routine returns the current dynamic length of the specified string.
If you're interested in obtaining the maximum length of the string, simply load the
accumulator with the first location of the string.

For example:

      LDA STRING ;Fetches maximum length of ;string.

The SUBSTR routine extracts a portion of a string and stores this substring into another
string variable.

The calling sequence for SUBSTR is:

      JSR SUBSTR
      ADR <source>,<index>,<length>,<destination>

where <source> is the name of the source string, <index> is the name of a SPEED/ASM
integer variable containing an index into the string where the first character of the
substring begins, <length> is the name of a SPEED/ASM variable that contains the length
of the substring to be extracted, and <destination> is the name of the string where the
substring is to be stored.

It is important to reiterate that <index> and <length> are the names of SPEED/ASM variables
that contain the respective values, not simply the values themselves.

If you try to place the value in the position for the index or length parameters,
strange things will happen.

If the substring you extract from the source string is too large to fit in the
destination string, SUBSTR returns the v flag set; otherwise the V flag is returned clear.

An example of the SUBSTR routine appears in Listing 2.

The INDEX routine lets you check if one string can be found within a larger one.
That is, if one string is a substring of another, INDEX returns a value giving you the
index of the source string within the second string.

The syntax for using INDEX is: JSR INDEX .

      ADR <key>,<source>

where<key> is the string you want to search for in the <source> string.
If the string is found, the index into <source> is returned in the 6502 accumulator.
If <key> is not present in <source>, then 0 is returned in the accumulator.

Listing 2 includes some examples of how to use INDEX.

The final SPEED/ASM string manipulation routine is CONCAT.
As you will surmise, it concatenates two strings.

The calling sequence is:

      JSR CONCAT
      ADR <SRC1>,<SRC2>,<DEST>

where <SRC1> and <SRC2> are the two source strings to be concatenated and <DEST?> is the
destination string where the result is to be stored.

<DEST> should not be the same string as <SRC1> or <SRC2>.

If the concatenated result is too large to fit into the destination string, it is
truncated and CONCAT returns with the v flag set.

If the concatenation was performed correctly the v flag is returned clear.

Examples of CONCAT appear in Listing 2.

Putting It All Together
This month’s example program (Listing 3)
is the beginning of a simple database/mailing list program using all the SPEED/ASM
constructs I’ve presented up to this point.

I'll expand on this program next month, so if it seems rather incomplete that’s only
because it is.

I have now surveyed all the string handling routines provided by the SPEED/ASM package.
While these routines provide most of the capabilities you'll require, some “pure” 6502 code
is often necessary to make a program run smoothly.

Next month, I'll describe the 6502 indexed and indirect addressing modes so you'll have
all the string capabilities you could possibly want.
